//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-24     ZhangYihua   first version
//
// Description  : 
//################################################################################

module sum_of_prdct #(     // sum of all products
parameter           CDW                     = 14,   // coefficient data width
parameter           CFW                     = 13,   // coefficient fractional width
parameter           IDW                     = 12,   // input data width
parameter           IFW                     = 9,    // input fractional width
parameter           ODW                     = 12,   // output data width
parameter           OFW                     = 9,    // output fractional width
parameter           COE_NUM                 = 10,   // COE_NUM>=1
parameter           IN_REG_EN               = 1'b0,
parameter           CSCD_LEN_MAX            = 4,    // CSCD_LEN_MAX>=2, cascade length, if too bigger, bad Fmax
parameter           CSCD_GRP                = COE_NUM/CSCD_LEN_MAX + (((COE_NUM%CSCD_LEN_MAX)==0) ? 0 : 1),
parameter           CSCD_LEN                = COE_NUM/CSCD_GRP + (((COE_NUM%CSCD_GRP)==0) ? 0 : 1),
parameter           LVL_NUM                 = (CSCD_GRP<=1) ? 1 : $clog2(CSCD_GRP),
parameter [LVL_NUM-1:0]     LVL_REG         = {LVL_NUM{1'b0}}   // specify register for each level
) ( 
input                                       rst_n,
input                                       clk,

input                                       in_vld,
input               [COE_NUM*CDW-1:0]       in_coe, // coefficients, {CN-1, ......, c1, c0}, c0 is far away from center
input               [COE_NUM*IDW-1:0]       in_dat, // datas {x(n-N+1), ......  x(n-1), x(n)}, DLY of x(n-1) > x(n)

output                                      out_vld,
output      signed  [ODW-1:0]               out_dat,    // y(n)
output                                      over
);

//  delay_num = sum(LVL_REG) + 1
//  y(n) = x(n)*c0 + x(n-1)*c1 + x(n-2)*c2 + .....  + x(n-N+1)*cN-1     (FIR algorithm formula)
// 
//           x(n)   _ x(n-1) _ x(n-2) _                  _ x(n-N+1)
//  PIPE    ------>|_|----->|_|----->|_|   .....  ----->|_|---
//              |   1    |   2    |   3                 N-1  |  
//  (outside)   |        |        |                          |
//  **********  | ****** | ****** |  **********************  | *********
//              |        |        |                          |
//         c0   V   c1   V   c2   V                    cN-1  V 
//  MUL    ---> *   ---> *   ---> *                     ---> *
//              |        |        |                          |
//              _        _        _                          _ 
//             |_|      |_|      |_|                        |_|
//              |        |        |        |                 |
//  LVL0        --->+<----        --->+<----   .....    +<----
//                  |                 |                 |
//  LVL1            -------->+<--------             -----
//                           |    ..........
//
//                                |         |
//  LVL_NUM-1                     ---->+<----
//                                     |
//                                     V
//  SUM                               y(n)

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          PRDCT_DW                = IDW+CDW;
localparam          CSCD_DW                 = PRDCT_DW+$clog2(CSCD_LEN);
localparam          SUM_DW                  = CSCD_DW+$clog2(CSCD_GRP);
localparam [COE_NUM-1:0]    CSCD_HEAD_MAP   = alloc_head_f(COE_NUM, CSCD_GRP);
localparam [COE_NUM-1:0]    CSCD_TAIL_MAP   = (CSCD_HEAD_MAP>>1) | (1'b1<<(COE_NUM-1));

reg         signed  [CDW-1:0]               coe_ary[COE_NUM-1:0];
reg         signed  [IDW-1:0]               dat_ary[COE_NUM-1:0];
wire        signed  [CSCD_DW-1:0]           cscd_ary[COE_NUM:0];
reg                                         prdct_vld;
reg                                         cscd_vld;
reg                 [CSCD_GRP*CSCD_DW-1:0]  cscd_tail;
wire        signed  [SUM_DW-1:0]            sum;

//################################################################################
// main
//################################################################################

always@(*) begin:ARY
    integer i;

    for(i=0; i<COE_NUM; i=i+1) begin
        dat_ary[i] = in_dat[i*IDW+:IDW];
        coe_ary[i] = in_coe[i*CDW+:CDW];
    end
end

assign cscd_ary[0] = {CSCD_DW{1'b0}};

genvar g1;
generate for (g1=0; g1<COE_NUM; g1=g1+1) begin:G_MUL_ADD
    // out_cscd = in_coe*in_dat + in_cscd;
    mul_add_cscd_wrap #(
        .CDW                            (CDW                            ),	// CDW<=19
        .IDW                            (IDW                            ),	// IDW<=18
        .VLD_USE                        (1'b1                           ),
        .PRE_ADD_EN                     (1'b0                           ),
        .IN_REG_EN                      (IN_REG_EN                      ),
        .P_REG_EN                       (1'b1                           ),
        .CSCD_REG_EN                    (CSCD_TAIL_MAP[g1]              ),
        .CSCD_DW                        (CSCD_DW                        ),
        .CSCD_HEAD                      (CSCD_HEAD_MAP[g1]              ),
        .CSCD_TAIL                      (CSCD_TAIL_MAP[g1]              )
    ) u_mul_add_cscd (
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .in_vld                         (in_vld                         ),
        .in_coe                         (coe_ary[g1]                    ),
        .in_dat                         (dat_ary[g1]                    ),
        .in_dat_2                       ({IDW{1'b0}}                    ),

        .in_cscd                        (cscd_ary[g1]                   ),
        .out_cscd                       (cscd_ary[g1+1]                 )
    );
end endgenerate

always@(*) begin:PICK_TAIL
    integer         s;
    integer         i;
    
    s = 0;
    for (i=0; i<COE_NUM; i=i+1) begin
        if (CSCD_TAIL_MAP[i]==1'b1) begin
            cscd_tail[s*CSCD_DW+:CSCD_DW] = cscd_ary[i+1];
            s = s + 1;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        prdct_vld <=`U_DLY 1'b0;
        cscd_vld  <=`U_DLY 1'b0;
    end else begin
        prdct_vld <=`U_DLY in_vld;
        cscd_vld  <=`U_DLY prdct_vld;
    end
end

add_all #(
        .OPRD_BW                        (CSCD_DW                        ),	// bit width of single operand
        .OPRD_NUM                       (CSCD_GRP                       ),	// number of operands, must 2<=OPRD_NUM<=16
        .OPRD_SIGNED                    (1'b1                           ),	// 1'b0: unsigned; 1'b1: signed;
        .LVL_NUM                        (LVL_NUM                        ),
        .LVL_REG                        (LVL_REG                        )
) u_add_all ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (1'b1                           ),

        .vld_in                         (cscd_vld                       ),
        .oprds                          (cscd_tail                      ),

        .vld_out                        (out_vld                        ),
        .sum_o                          (sum                            )
);

s_sat_tru #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (SUM_DW                         ),	// input data width
        .IFW                            (IFW+CFW                        ),	// input fractional width
        .ODW                            (ODW                            ),	// output data width
        .OFW                            (OFW                            )	// output fractional width
) u_out_dat ( 
        .id                             (sum                            ),	// s(IDW, IFW), the MSB is sign
        .od                             (out_dat                        ),	// s(ODW, OFW), the MSB is sign
        .over                           (over                           )
);

function [COE_NUM-1:0]  alloc_head_f;
    input integer n;
    input integer g;

    integer d, l;
    integer i, j, k;
    begin
        d = n/g;
        l = n%g;

        alloc_head_f = {COE_NUM{1'b0}};
        alloc_head_f[0] = 1'b1;
        j = 0;
        k = (j<l) ? d+1 : d;
        for(i=0; i<n; i=i+1) begin
            if (i==k) begin
                alloc_head_f[i] = 1'b1;
                j = j+1;
                k = k + ((j<l) ? d+1 : d);
            end
        end
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
